Advanced HTTP Handler Programming
HTTP
handlers are not a tool for everybody. They serve a very neat purpose:
changing the way a particular resource, or set of resources, is served
to the user. You can use handlers to filter out resources based on
runtime conditions or to apply any form of additional logic to the
retrieval of traditional resources such as pages and images. Finally,
you can use HTTP handlers to serve certain pages or resources in an
asynchronous manner.
For HTTP handlers, the
registration step is key. Registration enables ASP.NET to know about
your handler and its purpose. Registration is required for two practical
reasons. First, it serves to ensure that IIS forwards the call to the
correct ASP.NET application. Second, it serves to instruct your ASP.NET
application on the class to load to “handle” the request. As mentioned,
you can use handlers to override the processing of existing resources
(for example, hello.aspx) or to introduce new functionalities (for example, folder.axd). In both cases, you’re invoking a resource whose extension is already known to IIS—the .axd extension is registered in the IIS metabase when you install ASP.NET. In both cases, though, you need to modify the web.config file of the application to let the application know about the handler.
By using the ASHX extension and programming model for handlers, you can also save yourself the web.config update and deploy a new HTTP handler by simply copying a new file in a new or existing application’s folder.
Deploying Handlers as ASHX Resources
An alternative way to define an HTTP handler is through an .ashx file. The file contains a special directive, named @WebHandler, that expresses the association between the HTTP handler endpoint and the class used to implement the functionality. All .ashx files must begin with a directive like the following one:
<%@ WebHandler Language="C#" Class="Core35.Components.YourHandler" %>
When an .ashx endpoint is invoked, ASP.NET parses the source code of the file and figures out the HTTP handler class to use from the @WebHandler directive. This automation removes the need of updating the web.config file. Here’s a sample .ashx file. As you can see, it is the plain class file plus the special @WebHandler directive:
<%@ WebHandler Language="C#" Class="MyHandler" %>
using System.Web;
public class MyHandler : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
Note
that the source code of the class can either be specified inline or
loaded from any of the assemblies referenced by the application. When .ashx
resources are used to implement an HTTP handler, you just deploy the
source file, and you’re done. Just as for XML Web services, the source
file is loaded and compiled only on demand. Because ASP.NET adds a
special entry to the IIS metabase for .ashx resources, you don’t even need to enter changes to the Web server configuration.
Resources with an .ashx extension are handled by an HTTP handler class named SimpleHandleFactory. Note that SimpleHandleFactory is actually an HTTP handler factory class, not a simple HTTP handler class. We’ll discuss handler factories in a moment.
The SimpleHandleFactory class looks for the @WebHandler directive at the beginning of the file. The @WebHandler directive tells the handler factory the name of the HTTP handler class to instantiate once the source code has been compiled.
Important
You can build HTTP handlers both as regular class files compiled to an assembly and via .ashx resources. There’s no significant difference between the two approaches except that .ashx resources, like ordinary ASP.NET pages, will be compiled on the fly upon the first request. |
Prevent Access to Forbidden Resources
If your Web
application manages resources of a type that you don’t want to make
publicly available over the Web, you must instruct IIS not to display
those files. A possible way to accomplish this consists of forwarding
the request to aspnet_isapi and then binding the extension to one of the built-in handlers—the HttpForbiddenHandler class:
<add verb="*" path="*.xyz" type="System.Web.HttpForbiddenHandler" />
Any attempt to access an .xyz
resource results in an error message being displayed. The same trick
can also be applied for individual resources served by your application.
If you need to deploy, say, a text file but do not want to take the
risk that somebody can get to them, add the following:
<add verb="*" path="yourFile.txt" type="System.Web.HttpForbiddenHandler" />
Should It Be Reusable or Not?
In a conventional HTTP handler, the ProcessRequest method takes the lion’s share of the overall set of functionality. The second member of the IHttpHandler interface—the IsReusable property—is used only in particular circumstances. If you set the IsReusable property to return true, the handler is not unloaded from memory after use and is repeatedly used. Put another way, the Boolean value returned by IsReusable indicates whether the handler object can be pooled.
Frankly, most of the time it doesn’t really matter what you return—be it true or false. If you set the property to return false,
you require that a new object be allocated for each request. The simple
allocation of an object is not a particularly expensive operation.
However, the initialization of the handler might be costly. In this
case, by making the handler reusable, you save much of the overhead. If
the handler doesn’t hold any state, there’s no reason for not making it
reusable.
In summary, I’d say that IsReusable should be always set to true,
except when you have instance properties to deal with or properties
that might cause trouble if used in a concurrent environment. If you
have no initialization tasks, it doesn’t really matter whether it
returns true or false. As a margin note, the System.Web.UI.Page class—the most popular HTTP handler ever—sets its IsReusable property to false.
The key point to make is the following. Who’s really using IsReusable and, subsequently, who really cares about its value?
Once the HTTP runtime
knows the HTTP handler class to serve a given request, it simply
instantiates it—no matter what. So when is the IsReusable
property of a given handler taken into account? Only if you use an HTTP
handler factory—that is, a piece of code that dynamically decides which
handler should be used for a given request. An HTTP handler factory can
query a handler to determine whether the same instance can be used to
service multiple requests and thus optionally create and maintain a pool
of handlers.
ASP.NET pages and ASHX resources are served through factories. However, none of these factories ever checks IsReusable. Of all the built-in handler factories in the whole ASP.NET platform, very few check the IsReusable property of related handlers. So what’s the bottom line?
As long as you’re creating HTTP handlers for AXD, ASHX, or perhaps ASPX resources, be aware that the IsReusable
property is blissfully ignored. Do not waste your time trying to figure
out the optimal configuration. Instead, if you’re creating an HTTP
handler factory to serve a set of resources, whether or not to implement
a pool of handlers is up to you and IsReusable is the perfect tool for the job.
But
when should you employ an HTTP handler factory? In all situations in
which the HTTP handler class for a request is not uniquely identified.
For example, for ASPX pages, you don’t know in advance which HTTP
handler type you have to use. The type might not even exist (in which
case, you compile it on the fly). The HTTP handler factory is used
whenever you need to apply some logic to decide which is the right
handler to use. In other words, you need an HTTP handler factory when
declarative binding between endpoints and classes is not enough.
HTTP Handler Factories
An HTTP request can
be directly associated with an HTTP handler or with an HTTP handler
factory object. An HTTP handler factory is a class that implements the IHttpHandlerFactory interface and is in charge of returning the actual HTTP handler to use to serve the request. The SimpleHandlerFactory class provides a good example of how a factory works. The factory is mapped to requests directed at .ashx resources. When such a request comes in, the factory determines the actual handler to use by looking at the @WebHandler directive in the source file.
In the .NET
Framework, HTTP handler factories are used to perform some preliminary
tasks on the requested resource prior to passing it on to the handler.
Another good example of a handler factory object is represented by an
internal class named PageHandlerFactory, which is in charge of serving .aspx
pages. In this case, the factory handler figures out the name of the
handler to use and, if possible, loads it up from an existing assembly.
HTTP handler factories are classes that implement a couple of methods on the IHttpHandlerFactory interface—GetHandler and ReleaseHandler, as shown in Table 3.
Table 3. Members of the IHttpHandlerFactory Interface
Method | Description |
---|
GetHandler | Returns an instance of an HTTP handler to serve the request |
ReleaseHandler | Takes an existing HTTP handler instance and frees it up or pools it |
The GetHandler method has the following signature:
public virtual IHttpHandler GetHandler(HttpContext context,
string requestType, string url, string pathTranslated);
The requestType argument is a string that evaluates to GET or POST—the HTTP verb of the request. The last two arguments represent the raw URL of the request and the physical path behind it. The ReleaseHandler method is a mandatory override for any class that implements IHttpHandlerFactory; in most cases, it will just have an empty body.
The following listing shows a sample HTTP handler factory that returns different handlers based on the HTTP verb (GET or POST) used for the request:
class MyHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context,
string requestType, String url, String pathTranslated)
{
// Feel free to create a pool of HTTP handlers here
if(context.Request.RequestType.ToLower() == "get")
return (IHttpHandler) new MyGetHandler();
else if(context.Request.RequestType.ToLower() == "post")
return (IHttpHandler) new MyPostHandler();
return null;
}
public void ReleaseHandler(IHttpHandler handler)
{
// Nothing to do
}
}
When you use an HTTP
handler factory, it’s the factory, not the handler, that needs to be
registered with the ASP.NET configuration file. If you register the
handler, it will always be used to serve requests. If you opt for a
factory, you have a chance to decide dynamically and based on runtime
conditions which handler is more appropriate for a certain request. In
doing so, you can use the IsReusable property of handlers to implement a pool.
Asynchronous Handlers
An asynchronous HTTP handler is a class that implements the IHttpAsyncHandler interface. The system initiates the call by invoking the BeginProcessRequest
method. Next, when the method ends, a callback function is
automatically invoked to terminate the call. In the .NET Framework, the
sole HttpApplication class implements the asynchronous interface. The members of IHttpAsyncHandler interface are shown in Table 4.
Table 4. Members of the IHttpAsyncHandler Interface
Method | Description |
---|
BeginProcessRequest | Initiates an asynchronous call to the specified HTTP handler |
EndProcessRequest | Terminates the asynchronous call |
The signature of the BeginProcessRequest method is as follows:
IAsyncResult BeginProcessRequest(HttpContext context,
AsyncCallback cb, object extraData);
The context argument provides references to intrinsic server objects used to service HTTP requests. The second parameter is the AsyncCallback
object to invoke when the asynchronous method call is complete. The
third parameter is a generic cargo variable that contains any data you
might want to pass to the handler.
Note
An AsyncCallback
object is a delegate that defines the logic needed to finish processing
the asynchronous operation. A delegate is a class that holds a
reference to a method. A delegate class has a fixed signature, and it
can hold references only to methods that match that signature. A
delegate is equivalent to a type-safe function pointer or a callback. As
a result, an AsyncCallback object is just the code that executes when the asynchronous handler has completed its job. |
The AsyncCallback delegate has the following signature:
public delegate void AsyncCallback(IAsyncResult ar);
It uses the IAsyncResult
interface to obtain the status of the asynchronous operation. To
illustrate the plumbing of asynchronous handlers, I’ll show you the
pseudocode that the HTTP runtime employs when it deals with asynchronous
handlers. The HTTP runtime invokes the BeginProcessRequest method as illustrated by the following pseudocode:
// Sets an internal member of the HttpContext class with
// the current instance of the asynchronous handler
context.AsyncAppHandler = asyncHandler;
// Invokes the BeginProcessRequest method on the asynchronous HTTP handler
asyncHandler.BeginProcessRequest(context, OnCompletionCallback, context);
The context argument is the current instance of the HttpContext
class and represents the context of the request. A reference to the
HTTP context is also passed as the custom data sent to the handler to
process the request. The extraData parameter in the BeginProcessRequest signature is used to represent the status of the asynchronous operation. The BeginProcessRequest method returns an object of type HttpAsyncResult—a class that implements the IAsyncResult interface. The IAsyncResult interface contains a property named AsyncState that is set with the extraData value—in this case, the HTTP context.
The OnCompletionCallback
method is an internal method. It gets automatically triggered when the
asynchronous processing of the request terminates. The following listing
illustrates the pseudocode of the HttpRuntime private method:
// The method must have the signature of an AsyncCallback delegate
private void OnHandlerCompletion(IAsyncResult ar)
{
// The ar parameter is an instance of HttpAsyncResult
HttpContext context = (HttpContext) ar.AsyncState;
// Retrieves the instance of the asynchronous HTTP handler
// and completes the request
IHttpAsyncHandler asyncHandler = context.AsyncAppHandler;
asyncHandler.EndProcessRequest(ar);
// Finalizes the request as usual
...
}
The completion handler retrieves the HTTP context of the request through the AsyncState property of the IAsyncResult object it gets from the system. As mentioned, the actual object passed is an instance of the HttpAsyncResult class—in any case, it is the return value of the BeginProcessRequest method. The completion routine extracts the reference to the asynchronous handler from the context and issues a call to the EndProcessRequest method:
void EndProcessRequest(IAsyncResult result);
The EndProcessRequest method takes the IAsyncResult object returned by the call to BeginProcessRequest. As implemented in the HttpApplication class, the EndProcessRequest method does nothing special and is limited to throwing an exception if an error occurred.
Implementing Asynchronous Handlers
Asynchronous
handlers essentially serve one particular scenario—when the generation
of the markup is subject to lengthy operations, such as time-consuming
database stored procedures or calls to Web services. In these
situations, the ASP.NET thread in charge of the request is stuck waiting
for the operation to complete. Because the thread is a valuable member
of the ASP.NET thread pool, lengthy tasks are potentially the perfect
scalability killer. However, asynchronous handlers are here to help.
The idea is that the request begins on a thread-pool thread, but that thread is released as soon as the operation begins. In BeginProcessRequest, you typically create your own thread and start the lengthy operation. BeginProcessRequest doesn’t wait for the operation to complete; therefore, the thread is returned to the pool immediately.
There are a lot of
tricky details that this bird’s-eye description just omitted. In the
first place, you should strive to avoid a proliferation of threads.
Ideally, you should use a custom thread pool. Furthermore, you must
figure out a way to signal when the lengthy operation has terminated.
This typically entails creating a custom class that implements IAsyncResult and returning it from BeginProcessRequest. This class embeds a synchronization object—typically a ManualResetEvent object—that the custom thread carrying the work will signal upon completion.
In the end, building
asynchronous handlers is definitely tricky and not for novice
developers. Very likely, you are more interested in asynchronous pages
than in asynchronous HTTP handlers—that is, the same mechanism but
applied to .aspx resources. In this case, the “lengthy task” is merely the ProcessRequest method of the Page
class. (Obviously, you configure the page to execute asynchronously
only if the page contains code that might start I/O-bound and
potentially lengthy operations.)
Warning
I’ve seen several ASP.NET developers using an .aspx
page to serve markup other than HTML markup. This is not a good idea.
An .aspx resource is served by quite a rich and sophisticated HTTP
handler—the System.Web.UI.Page class. The ProcessRequest method of this class entirely provides for the page life cycle as we know it—Init, Load, and PreRender
events, as well as rendering stage, view state, and postback
management. Nothing of the kind is really required if you only need to
retrieve and return, say, the bytes of an image. |